Unity3D Building Damage Tutorial- Part VII - Optimalisation
So, we have to make code a little bit faster. To count the time taken by things we are going to speed up (Destroy method), I've written this: public float DebDestroyTime = 0f; public float DebGeneralDestroyTime = 0f; public int DebDestroyCount = 0; public int DebGeneralNumber = 0; ... ... ... ... public void TellAliasesToUpdate(){ DebDestroyCount = 0; DebDestroyTime = 0; if(time>3){ for(int k=0; k0){ DebGeneralNumber++; DebGeneralDestroyTime += DebDestroyTime; Debug.Log(DebGeneralNumber+" The overall time is "+DebDestroyTime+" And the General time is "+DebGeneralDestroyTime); } } Don't bother about the time variable. Things for counting optimalisation time have Deb prefix. *DebGeneralNumber Is the number of Updates where some kind of destroying was done. It is not done allways, as in many "updates" the Aliases are just losing health *DebDestroyTime Time consumed for Destroy methods in every Alias in one Update. *DebGeneralDestroyTimeTime consumed for Destroy methods in every Alias in all Updates *DebDestroyCount The number of times Destroy method was called in one Update This variables are set in Destroy method in Verticle class private void Destroy(){ float time = 0f; time = Time.realtimeSinceStartup; //Here setting current time for(int i = 0; i//Now counting deffrence //Debug.Log("Time made is "+time); OwnerManager.DebDestroyTime += time; //and adding to DebDestroyTime variables OwnerManager.DebDestroyCount++; } Okay, so now (for me) time after 10 "destroing" updates is is 14.10584 seconds. Making only 2 traingles for a wall As you may remember from last part, for every wall I made 4 traingles, effectively making it visible for two sides. This is of course unnecessary, and time-consuming. As we know traingle data in Unity in stored in mesh.traingles array. It consists of ints, every three of them mean one traingle with verticles of this 3 numbers. I discovered that to correctly face the traingle, we have to know the position of our destroyed Alias among the three. Than, knowing the order we can set the order of Aliases in AddTrinagleAndVerticles method. This is how I've done it: private void ProduceTrianglesBetweenWalls(){//fills the void between Alias and its twin int[] TempListOfAliasNumbers= new int2; //Stores the numbers of other (not this) aliases that form a triangle int DestroyedAliasPosition = 0; foreach(int k in Triangles){ if(k < OwnerManager.OrgTriangleCount){//This triangle is not a wall triangle Vector3 VertNumbers = OwnerManager.Trianglesk; if(VertNumbers != Vector3.zero){ int i = 0; for(int j=0; j<3; j++){ float f = VertNumbersj; if(OwnerManager.VerticleToAliasArray[ (int) f]!= number){ int z = OwnerManager.VerticleToAliasArray[ (int) f]; TempListOfAliasNumbersi = z; i++; }else{ DestroyedAliasPosition = i; Debug.Log(i);} } if( (OwnerManager.Aliases[TempListOfAliasNumbers0].state != VerticleState.Destroyed) && (OwnerManager.Aliases[TempListOfAliasNumbers1].state != VerticleState.Destroyed) ){ AddTriangleInOrder(DestroyedAliasPosition, TempListOfAliasNumbers); } } } } OwnerManager.TranslateFromMeshToManager(); } private void AddTriangleInOrder(int DestroyedAliasPosition, int[] TempListOfAliasNumbers){ switch (DestroyedAliasPosition){ case 0: OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0) ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; break; case 1: OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 ,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2) ; break; case 2: OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 ) ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; break; } } *DestroyedAliasPosition Stores this position. It can be either 0, 1 or 2. Than, it is passed with TempListOfAliasNumbers array to the AddTriangleInOrder method. There, adequate order is found. (Order was found by just trying until it was good) With that, the time of 10 "destroying" updates fell to 10.63108 for me. Betted, but still far to slow. TranslateFromMeshToManager and UpdateTrianglesList Let's analise the Destroy method in Verticle class. private void Destroy(){ //DebEnlightenThisAlias(); //DebEnlightenTriangles(); //Debug.Log("liAl "+LinkedAliases0+" Da angle "+number); for(int i = 0; iUpdateTrianglesList. It is called each time the Alias is being Destroyed. This means that it is run many times in every "destroing" update. private void ProduceTrianglesBetweenWalls(){//fills the void between Alias and its twin int[] TempListOfAliasNumbers= new int2{99,99}; //Stores the numbers of other (not this) aliases that form a triangle float[] fx = new float2; foreach(int k in Triangles){ if(k < OwnerManager.OrgTriangleCount){//This triangle is not a wall triangle Vector3 VertNumbers = OwnerManager.Trianglesk; if(VertNumbers != Vector3.zero){ int i = 0; for(int j=0; j<3; j++){ float f = VertNumbersj; if(OwnerManager.VerticleToAliasArray[ (int) f]!= number){ fxi = f; int z = OwnerManager.VerticleToAliasArray[ (int) f]; TempListOfAliasNumbersi = z; i++; } } if( (OwnerManager.Aliases[TempListOfAliasNumbers0].state != VerticleState.Destroyed) && (OwnerManager.Aliases[TempListOfAliasNumbers1].state != VerticleState.Destroyed) ){ OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 ,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 ) ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2) ; OwnerManager.AddTriangleAndVerticles(TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; } } } } OwnerManager.TranslateFromMeshToManager(); } This function, as we know manages the wall making. As triangles and vertices are maniplated directly in mesh class. After that, these changes have to be "translated" to my own custom classes. And this "translation", made in TranslateFromMeshToManager(); is made every time a wall is bulid. So, in an one "destroying" update, we have multiple calls to functions UpdateTrianglesList(); and TranslateFromMeshToManager();. Why not calling them only in the end of TellAliasesToUpdate method? This for sure will speed things up! TellAliasesToUpdate method(MeshManager.cs) public bool MeshWasChanged = false; ... ... ... ... ... ... ... public void TellAliasesToUpdate(){ DebDestroyCount = 0; DebDestroyTime = 0; if(time>1){ for(int k=0; k0){ DebGeneralNumber++; DebGeneralDestroyTime += DebDestroyTime; Debug.Log(DebGeneralNumber+" The overall time is "+DebDestroyTime+" And the General time is "+DebGeneralDestroyTime); } } Well, two two new things: *Two make code even faster, i added MeshWasChanged bool. It works similarly to the DebDestroyCount. In Destroy method in Verticle, we change it to true. In this way, only when some changes are made , that part of code will work. *MakeWallsFromList I will talk about it in a moment. For now, we have to change the UpdateTrianglesList(); UpdateTrainglesList Method (MeshManager.cs) public void UpdateTrianglesList(){//gets the in-manager triangle list and then translates it to mesh.triangles format, and then sets it to be the mesh.triangles. If mesh.traingles is longer than Traingles*3, writes from 0 to traingles*3, rest of mesh.traingles is left unchanged //Debug.Log("UPL"); int LenghtOfTrianglesArray = Triangles.Count*3; int[] tempTrianglesArray = new intLenghtOfTrianglesArray; int i = 0; foreach(Vector3 vec in Triangles){ tempTrianglesArrayi = (int)vec.x; tempTrianglesArrayi+1 = (int)vec.y; tempTrianglesArrayi+2 = (int)vec.z; i+=3; } int[] tempTrianglesArrayNumber2 = new intmesh.triangles.Length; mesh.triangles.CopyTo(tempTrianglesArrayNumber2,0); tempTrianglesArray.CopyTo(tempTrianglesArrayNumber2, 0);// we override the data in tempTrianglesArrayNumber2, but only to a lenght of TempTrianglesArray mesh.triangles = tempTrianglesArrayNumber2; //mesh.triangles = tempTrianglesArray; } Well, I've added a new tempTrianglesArrayNumber2 array which is of current mesh.traingle lenght. Important here is that if here the lenght of mesh.triangles is bigger that Traingles list * 3, the additional traingles (from adding a wall) will be preserved, and not deleted. Problem with walls. Well, if we would start the game by now, without the MakeWallsFromList, something like that would happen: The reason for that is this: Sometimes, in the same update a new Wall is added, using lets say Alias A, and the same Alias is deleted. Becouse that the mesh->Classes translate is done in the end of update, the Alias A doesn't know that he "takes part" in wall, and in result fails to remove it. This is the solution that i found for this problem: #While being destroyed, the Aliases will not call AddTriangleAndVerticles directly, but will add data about this theoretical wall to a special list instead. #After all update is done, code will check for every wall in list if Aliases taking part in it are still "undestroyed". If so, the wall will be made #Clears the List. Okey, let's go down to implementation. Alternative wall making // in Vertices Class private void AddTriangleInOrder(int DestroyedAliasPosition, int[] TempListOfAliasNumbers){ switch (DestroyedAliasPosition){ case 0: OwnerManager.AddWallToWallsList(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0) ; OwnerManager.AddWallToWallsList(TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; break; case 1: OwnerManager.AddWallToWallsList(TempListOfAliasNumbers0 ,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; OwnerManager.AddWallToWallsList(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2) ; break; case 2: OwnerManager.AddWallToWallsList(TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 ) ; OwnerManager.AddWallToWallsList(TempListOfAliasNumbers1 + OwnerManager.Aliases.Count/2,TempListOfAliasNumbers1, TempListOfAliasNumbers0 + OwnerManager.Aliases.Count/2) ; break; } } Simple change from AddTriangleAndVerticles to AddWallToWallsList. AddWallToWallsList method (meshManager) public List WallsWaitingToBeMade = new List(); ... ... ... ... ... ... public void AddWallToWallsList(int a, int b, int c){ WallsWaitingToBeMade.Add(a); WallsWaitingToBeMade.Add(b); WallsWaitingToBeMade.Add©; } Well, just adding the Aliases numbers to the List. (the "format" of triangles in this list is similar to mesh.traingles: 3 ints one after another mean one traingle. Than, in TellAliasesToUpdate method (MeshManager class) we call MakeWallsFromList MakeWallsFromList method private void MakeWallsFromList(){//this check if verticles of teoretical Alias are still Ok. If so, sends info to make trinagle from them. TempMeshVerticlesArray = new Vector3mesh.vertices.Length; mesh.vertices.CopyTo(TempMeshVerticlesArray,0); TempMeshTrinaglesArray = new intmesh.triangles.Length; mesh.triangles.CopyTo(TempMeshTrinaglesArray,0); float time = Time.realtimeSinceStartup; for(int i=0; i=Aliases.Count/2){//it is a Twin!, so dont have state if( Aliases[WallsWaitingToBeMadei-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } if(WallsWaitingToBeMadei+1=Aliases.Count/2){//it is a Twin!, so dont have state if( Aliases[WallsWaitingToBeMadei+1-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } if(WallsWaitingToBeMadei+2=Aliases.Count/2){//it is a Twin!, so dont have state if( Aliases[WallsWaitingToBeMadei+2-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } if(ItIsOk true){ AddTriangleAndVerticles( WallsWaitingToBeMadei, WallsWaitingToBeMadei+1, WallsWaitingToBeMadei+2); } } WallsWaitingToBeMade.Clear(); mesh.vertices = TempMeshVerticlesArray; mesh.triangles = TempMeshTrinaglesArray; DebDestroyTime += (Time.realtimeSinceStartup - time); } Might look a bit complicated, but it is really simple *This copying on the top is something else, will talk bout it later for(int i=0; i First, there is a loop that will go through all traingles in WallsWaitingToBeMade List. if(WallsWaitingToBeMadei=Aliases.Count/2){//it is a Twin!, so dont have state if( Aliases[WallsWaitingToBeMadei-Aliases.Count/2].state != VerticleState.Destroyed ){//check the Alias //well, do nothin }else{ItIsOk = false;} }else{ItIsOk = false;} } Here is a condition that checks if the Alias which will take part in wall-making is not destroyed. But is not so easy, as twins may take part too, and as we know they dont have states(or at least, states are not updated). *WallsWaitingToBeMadei Check if it is an Alias **Aliases[WallsWaitingToBeMadei].state != VerticleState.Destroyed which is not destroyed. If so, it is ok, lets do nothin *if(WallsWaitingToBeMadei>=Aliases.Count/2) It is a twin, it don't have a state, so **Aliases[WallsWaitingToBeMadei-Aliases.Count/2].state != VerticleState.Destroyed We check the alias which twin it is, if it is destroyed. Accoring to rules i stated, the Alias and its twin have the same states. If conditions went otherwise, I set a bool ItIsOk = false;. After all 3 Aliases are checked in that manner, and ItIsOk is still true like in the beginning, we can make a wall. if(ItIsOk true){ AddTriangleAndVerticles( WallsWaitingToBeMadei, WallsWaitingToBeMadei+1, WallsWaitingToBeMadei+2); } Than, the list is cleared WallsWaitingToBeMade.Clear(); With that optimalisation, the DebDestroyTime after 10 updates fell to 0.5745029 sec. Enormous change, but there is one more big thing to optimalise Setting mesh.vertices and mesh.traingles I suspect (but i am not sure), that when we set mesh.vertices or mesh.traingles not only reference to an array is changed, but there are some inter-Mesh calculations taking place. (like normals counting). Now, these small calculations are performed every time AddTriangleAndVerticles is called. Therefore, it would be faster to first produce an big array with added verticle and traingle arrays, and that set the mesh.traingles and vertices. So, I added two new temporary arrays private int[] TempMeshTrinaglesArray; private Vector3[] TempMeshVerticlesArray; Initialised them at begining of MakeWallsFromList method private void MakeWallsFromList(){//this check if verticles of teoretical Alias are still Ok. If so, sends info to make trinagle from them. TempMeshVerticlesArray = new Vector3mesh.vertices.Length; mesh.vertices.CopyTo(TempMeshVerticlesArray,0); TempMeshTrinaglesArray = new intmesh.triangles.Length; mesh.triangles.CopyTo(TempMeshTrinaglesArray,0); ... ... ... ... ... ... ... ... ... ... ... And Edited the AddTriangleAndVerticles method, for it to operate on Temporary Arrays. public int AddTriangleAndVerticles(int a, int b, int c){//gets 3 numbers of Aliases. Produces a triangle between them, and new verticles Vector3[] OldVerticleList = TempMeshVerticlesArray; Vector3[] NewVerticleList = new Vector3 TempMeshVerticlesArray.Length+3; Vector3[] LastThreeVerts = new Vector33{Aliasesa.positionRelative,Aliasesb.positionRelative,Aliasesc.positionRelative }; OldVerticleList.CopyTo(NewVerticleList,0); LastThreeVerts.CopyTo(NewVerticleList, TempMeshVerticlesArray.Length); TempMeshVerticlesArray = NewVerticleList; int[] OldTriangleList = TempMeshTrinaglesArray; int[] NewTriangleList = new int TempMeshTrinaglesArray.Length+3; int[] NewTriangle = new int3{TempMeshVerticlesArray.Length-1, TempMeshVerticlesArray.Length-2, TempMeshVerticlesArray.Length-3}; OldTriangleList.CopyTo(NewTriangleList, 0); NewTriangle.CopyTo(NewTriangleList, OldTriangleList.Length); TempMeshTrinaglesArray = NewTriangleList; return (TempMeshTrinaglesArray.Length/3)-1; //the number of added triangle } And then, on the end of MakeWallsFromList method, we update the mesh ... ... ... ... ... WallsWaitingToBeMade.Clear(); mesh.vertices = TempMeshVerticlesArray; mesh.triangles = TempMeshTrinaglesArray; DebDestroyTime += (Time.realtimeSinceStartup - time); } Okay, and it is end. Time of 10 updates is 0.1333022 sec. For now, it is good enough. Of course, there are many other places to optimalise, but for now, I'm done. For files, goto Github, Version 5. Category:Unity3D Building Damage Tutorial Category:Unity3D Category:C Sharp